#include "wx/wxprec.h"

#if wxUSE_ANIMATIONCTRL && (!defined(__WXGTK20__) || defined(__WXUNIVERSAL__))

#include "wx/animate.h"

#ifndef WX_PRECOMP
#include "wx/log.h"
#include "wx/image.h"
#include "wx/dcmemory.h"
#include "wx/dcclient.h"
#include "wx/module.h"
#endif

#include "wx/wfstream.h"
#include "wx/gifdecod.h"
#include "wx/anidecod.h"

#include "wx/listimpl.cpp"
WX_DEFINE_LIST( wxAnimationDecoderList )

wxAnimationDecoderList wxAnimation::sm_handlers;


// ----------------------------------------------------------------------------
// wxAnimation
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS( wxAnimation, wxAnimationBase )
#define M_ANIMDATA      wx_static_cast(wxAnimationDecoder*, m_refData)

wxSize wxAnimation::GetSize() const {
  wxCHECK_MSG( IsOk(), wxDefaultSize, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetAnimationSize();
}

unsigned int wxAnimation::GetFrameCount() const {
  wxCHECK_MSG( IsOk(), 0, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetFrameCount();
}

wxImage wxAnimation::GetFrame( unsigned int i ) const {
  wxCHECK_MSG( IsOk(), wxNullImage, wxT( "invalid animation" ) );
  wxImage ret;
  if( !M_ANIMDATA->ConvertToImage( i, &ret ) ) {
    return wxNullImage;
  }
  return ret;
}

int wxAnimation::GetDelay( unsigned int i ) const {
  wxCHECK_MSG( IsOk(), 0, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetDelay( i );
}

wxPoint wxAnimation::GetFramePosition( unsigned int frame ) const {
  wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetFramePosition( frame );
}

wxSize wxAnimation::GetFrameSize( unsigned int frame ) const {
  wxCHECK_MSG( IsOk(), wxDefaultSize, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetFrameSize( frame );
}

wxAnimationDisposal wxAnimation::GetDisposalMethod( unsigned int frame ) const {
  wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetDisposalMethod( frame );
}

wxColour wxAnimation::GetTransparentColour( unsigned int frame ) const {
  wxCHECK_MSG( IsOk(), wxNullColour, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetTransparentColour( frame );
}

wxColour wxAnimation::GetBackgroundColour() const {
  wxCHECK_MSG( IsOk(), wxNullColour, wxT( "invalid animation" ) );
  return M_ANIMDATA->GetBackgroundColour();
}

bool wxAnimation::LoadFile( const wxString& filename, wxAnimationType type ) {
  wxFileInputStream stream( filename );
  if( !stream.IsOk() ) {
    return false;
  }
  return Load( stream, type );
}

bool wxAnimation::Load( wxInputStream &stream, wxAnimationType type ) {
  UnRef();
  const wxAnimationDecoder *handler;
  if( type == wxANIMATION_TYPE_ANY ) {
    for( wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
         node; node = node->GetNext() ) {
      handler = ( const wxAnimationDecoder* )node->GetData();
      if( handler->CanRead( stream ) ) {
        // do a copy of the handler from the static list which we will own
        // as our reference data
        m_refData = handler->Clone();
        return M_ANIMDATA->Load( stream );
      }
    }
    wxLogWarning( _( "No handler found for animation type." ) );
    return false;
  }
  handler = FindHandler( type );
  // do a copy of the handler from the static list which we will own
  // as our reference data
  m_refData = handler->Clone();
  if( handler == NULL ) {
    wxLogWarning( _( "No animation handler for type %ld defined." ), type );
    return false;
  }
  if( stream.IsSeekable() && !M_ANIMDATA->CanRead( stream ) ) {
    wxLogError( _( "Animation file is not of type %ld." ), type );
    return false;
  } else
  { return M_ANIMDATA->Load( stream ); }
}


// ----------------------------------------------------------------------------
// animation decoders
// ----------------------------------------------------------------------------

void wxAnimation::AddHandler( wxAnimationDecoder *handler ) {
  // Check for an existing handler of the type being added.
  if( FindHandler( handler->GetType() ) == 0 ) {
    sm_handlers.Append( handler );
  } else {
    // This is not documented behaviour, merely the simplest 'fix'
    // for preventing duplicate additions.  If someone ever has
    // a good reason to add and remove duplicate handlers (and they
    // may) we should probably refcount the duplicates.
    wxLogDebug( _T( "Adding duplicate animation handler for '%d' type" ),
                handler->GetType() );
    delete handler;
  }
}

void wxAnimation::InsertHandler( wxAnimationDecoder *handler ) {
  // Check for an existing handler of the type being added.
  if( FindHandler( handler->GetType() ) == 0 ) {
    sm_handlers.Insert( handler );
  } else {
    // see AddHandler for additional comments.
    wxLogDebug( _T( "Inserting duplicate animation handler for '%d' type" ),
                handler->GetType() );
    delete handler;
  }
}

const wxAnimationDecoder *wxAnimation::FindHandler( wxAnimationType animType ) {
  wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
  while( node ) {
    const wxAnimationDecoder *handler = ( const wxAnimationDecoder * )node->GetData();
    if( handler->GetType() == animType ) {
      return handler;
    }
    node = node->GetNext();
  }
  return 0;
}

void wxAnimation::InitStandardHandlers() {
  AddHandler( new wxGIFDecoder );
  AddHandler( new wxANIDecoder );
}

void wxAnimation::CleanUpHandlers() {
  wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
  while( node ) {
    wxAnimationDecoder *handler = ( wxAnimationDecoder * )node->GetData();
    wxAnimationDecoderList::compatibility_iterator next = node->GetNext();
    delete handler;
    node = next;
  }
  sm_handlers.Clear();
}


// A module to allow wxAnimation initialization/cleanup
// without calling these functions from app.cpp or from
// the user's application.

class wxAnimationModule: public wxModule {
    DECLARE_DYNAMIC_CLASS( wxAnimationModule )
  public:
    wxAnimationModule() {}
    bool OnInit() { wxAnimation::InitStandardHandlers(); return true; }
    void OnExit() { wxAnimation::CleanUpHandlers(); }
};

IMPLEMENT_DYNAMIC_CLASS( wxAnimationModule, wxModule )


// ----------------------------------------------------------------------------
// wxAnimationCtrl
// ----------------------------------------------------------------------------

IMPLEMENT_CLASS( wxAnimationCtrl, wxAnimationCtrlBase )
BEGIN_EVENT_TABLE( wxAnimationCtrl, wxAnimationCtrlBase )
  EVT_PAINT( wxAnimationCtrl::OnPaint )
  EVT_SIZE( wxAnimationCtrl::OnSize )
  EVT_TIMER( wxID_ANY, wxAnimationCtrl::OnTimer )
END_EVENT_TABLE()

void wxAnimationCtrl::Init() {
  m_currentFrame = 0;
  m_looped = false;
  m_isPlaying = false;
  // use the window background colour by default to be consistent
  // with the GTK+ native version
  m_useWinBackgroundColour = true;
}

bool wxAnimationCtrl::Create( wxWindow *parent, wxWindowID id,
                              const wxAnimation& animation, const wxPoint& pos,
                              const wxSize& size, long style, const wxString& name ) {
  m_timer.SetOwner( this );
  if( !base_type::Create( parent, id, pos, size, style, wxDefaultValidator, name ) ) {
    return false;
  }
  // by default we get the same background colour of our parent
  SetBackgroundColour( parent->GetBackgroundColour() );
  SetAnimation( animation );
  return true;
}

wxAnimationCtrl::~wxAnimationCtrl() {
  Stop();
}

bool wxAnimationCtrl::LoadFile( const wxString& filename, wxAnimationType type ) {
  wxAnimation anim;
  if( !anim.LoadFile( filename, type ) ||
      !anim.IsOk() ) {
    return false;
  }
  SetAnimation( anim );
  return true;
}

wxSize wxAnimationCtrl::DoGetBestSize() const {
  if( m_animation.IsOk() && !this->HasFlag( wxAC_NO_AUTORESIZE ) ) {
    return m_animation.GetSize();
  }
  return wxSize( 100, 100 );
}

void wxAnimationCtrl::SetAnimation( const wxAnimation& animation ) {
  if( IsPlaying() ) {
    Stop();
  }
  // set new animation even if it's wxNullAnimation
  m_animation = animation;
  if( !m_animation.IsOk() ) {
    DisplayStaticImage();
    return;
  }
  if( m_animation.GetBackgroundColour() == wxNullColour ) {
    SetUseWindowBackgroundColour();
  }
  if( !this->HasFlag( wxAC_NO_AUTORESIZE ) ) {
    FitToAnimation();
  }
  DisplayStaticImage();
}

void wxAnimationCtrl::SetInactiveBitmap( const wxBitmap &bmp ) {
  // if the bitmap has an associated mask, we need to set our background to
  // the colour of our parent otherwise when calling DrawCurrentFrame()
  // (which uses the bitmap's mask), our background colour would be used for
  // transparent areas - and that's not what we want (at least for
  // consistency with the GTK version)
  if( bmp.GetMask() != NULL && GetParent() != NULL ) {
    SetBackgroundColour( GetParent()->GetBackgroundColour() );
  }
  wxAnimationCtrlBase::SetInactiveBitmap( bmp );
}

void wxAnimationCtrl::FitToAnimation() {
  SetSize( m_animation.GetSize() );
}

bool wxAnimationCtrl::SetBackgroundColour( const wxColour& colour ) {
  if( !wxWindow::SetBackgroundColour( colour ) ) {
    return false;
  }
  // if not playing, then this change must be seen immediately (unless
  // there's an inactive bitmap set which has higher priority than bg colour)
  if( !IsPlaying() ) {
    DisplayStaticImage();
  }
  return true;
}


// ----------------------------------------------------------------------------
// wxAnimationCtrl - stop/play methods
// ----------------------------------------------------------------------------

void wxAnimationCtrl::Stop() {
  m_timer.Stop();
  m_isPlaying = false;
  // reset frame counter
  m_currentFrame = 0;
  DisplayStaticImage();
}

bool wxAnimationCtrl::Play( bool looped ) {
  if( !m_animation.IsOk() ) {
    return false;
  }
  m_looped = looped;
  m_currentFrame = 0;
  if( !RebuildBackingStoreUpToFrame( 0 ) ) {
    return false;
  }
  m_isPlaying = true;
  // do a ClearBackground() to avoid that e.g. the custom static bitmap which
  // was eventually shown previously remains partially drawn
  ClearBackground();
  // DrawCurrentFrame() will use our updated backing store
  wxClientDC clientDC( this );
  DrawCurrentFrame( clientDC );
  // start the timer
  int delay = m_animation.GetDelay( 0 );
  if( delay == 0 ) {
    delay = 1;
  }      // 0 is invalid timeout for wxTimer.
  m_timer.Start( delay, true );
  return true;
}



// ----------------------------------------------------------------------------
// wxAnimationCtrl - rendering methods
// ----------------------------------------------------------------------------

bool wxAnimationCtrl::RebuildBackingStoreUpToFrame( unsigned int frame ) {
  // if we've not created the backing store yet or it's too
  // small, then recreate it
  wxSize sz = m_animation.GetSize(),
         winsz = GetClientSize();
  int w = wxMin( sz.GetWidth(), winsz.GetWidth() );
  int h = wxMin( sz.GetHeight(), winsz.GetHeight() );
  if( !m_backingStore.IsOk() ||
      m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h ) {
    if( !m_backingStore.Create( w, h ) ) {
      return false;
    }
  }
  wxMemoryDC dc;
  dc.SelectObject( m_backingStore );
  // Draw the background
  DisposeToBackground( dc );
  // Draw all intermediate frames that haven't been removed from the animation
  for( unsigned int i = 0; i < frame; i++ ) {
    if( m_animation.GetDisposalMethod( i ) == wxANIM_DONOTREMOVE ||
        m_animation.GetDisposalMethod( i ) == wxANIM_UNSPECIFIED ) {
      DrawFrame( dc, i );
    } else if( m_animation.GetDisposalMethod( i ) == wxANIM_TOBACKGROUND )
      DisposeToBackground( dc, m_animation.GetFramePosition( i ),
                           m_animation.GetFrameSize( i ) );
  }
  // finally draw this frame
  DrawFrame( dc, frame );
  dc.SelectObject( wxNullBitmap );
  return true;
}

void wxAnimationCtrl::IncrementalUpdateBackingStore() {
  wxMemoryDC dc;
  dc.SelectObject( m_backingStore );
  // OPTIMIZATION:
  // since wxAnimationCtrl can only play animations forward, without skipping
  // frames, we can be sure that m_backingStore contains the m_currentFrame-1
  // frame and thus we just need to dispose the m_currentFrame-1 frame and
  // render the m_currentFrame-th one.
  if( m_currentFrame == 0 ) {
    // before drawing the first frame always dispose to bg colour
    DisposeToBackground( dc );
  } else {
    switch( m_animation.GetDisposalMethod( m_currentFrame - 1 ) ) {
      case wxANIM_TOBACKGROUND:
        DisposeToBackground( dc, m_animation.GetFramePosition( m_currentFrame - 1 ),
                             m_animation.GetFrameSize( m_currentFrame - 1 ) );
        break;
      case wxANIM_TOPREVIOUS:
        // this disposal should never be used too often.
        // E.g. GIF specification explicitely say to keep the usage of this
        //      disposal limited to the minimum.
        // In fact it may require a lot of time to restore
        if( m_currentFrame == 1 ) {
          // if 0-th frame disposal is to restore to previous frame,
          // the best we can do is to restore to background
          DisposeToBackground( dc );
        } else if( !RebuildBackingStoreUpToFrame( m_currentFrame - 2 ) ) {
          Stop();
        }
        break;
      case wxANIM_DONOTREMOVE:
      case wxANIM_UNSPECIFIED:
        break;
    }
  }
  // now just draw the current frame on the top of the backing store
  DrawFrame( dc, m_currentFrame );
  dc.SelectObject( wxNullBitmap );
}

void wxAnimationCtrl::DisplayStaticImage() {
  wxASSERT( !IsPlaying() );
  // m_bmpStaticReal will be updated only if necessary...
  UpdateStaticImage();
  if( m_bmpStaticReal.IsOk() ) {
    // copy the inactive bitmap in the backing store
    // eventually using the mask if the static bitmap has one
    if( m_bmpStaticReal.GetMask() ) {
      wxMemoryDC temp;
      temp.SelectObject( m_backingStore );
      DisposeToBackground( temp );
      temp.DrawBitmap( m_bmpStaticReal, 0, 0, true /* use mask */ );
    } else
    { m_backingStore = m_bmpStaticReal; }
  } else {
    // put in the backing store the first frame of the animation
    if( !m_animation.IsOk() ||
        !RebuildBackingStoreUpToFrame( 0 ) ) {
      m_animation = wxNullAnimation;
      DisposeToBackground();
    }
  }
  Refresh();
}

void wxAnimationCtrl::DrawFrame( wxDC &dc, unsigned int frame ) {
  // PERFORMANCE NOTE:
  // this draw stuff is not as fast as possible: the wxAnimationDecoder
  // needs first to convert from its internal format to wxImage RGB24;
  // the wxImage is then converted as a wxBitmap and finally blitted.
  // If wxAnimationDecoder had a function to convert directly from its
  // internal format to a port-specific wxBitmap, it would be somewhat faster.
  wxBitmap bmp( m_animation.GetFrame( frame ) );
  dc.DrawBitmap( bmp, m_animation.GetFramePosition( frame ),
                 true /* use mask */ );
}

void wxAnimationCtrl::DrawCurrentFrame( wxDC& dc ) {
  wxASSERT( m_backingStore.IsOk() );
  // m_backingStore always contains the current frame
  dc.DrawBitmap( m_backingStore, 0, 0, true /* use mask in case it's present */ );
}

void wxAnimationCtrl::DisposeToBackground() {
  // clear the backing store
  wxMemoryDC dc;
  dc.SelectObject( m_backingStore );
  if( dc.IsOk() ) {
    DisposeToBackground( dc );
  }
}

void wxAnimationCtrl::DisposeToBackground( wxDC& dc ) {
  wxColour col = IsUsingWindowBackgroundColour()
                 ? GetBackgroundColour()
                 : m_animation.GetBackgroundColour();
  wxBrush brush( col );
  dc.SetBackground( brush );
  dc.Clear();
}

void wxAnimationCtrl::DisposeToBackground( wxDC& dc, const wxPoint &pos, const wxSize &sz ) {
  wxColour col = IsUsingWindowBackgroundColour()
                 ? GetBackgroundColour()
                 : m_animation.GetBackgroundColour();
  wxBrush brush( col );
  dc.SetBrush( brush );       // SetBrush and not SetBackground !!
  dc.SetPen( *wxTRANSPARENT_PEN );
  dc.DrawRectangle( pos, sz );
}

// ----------------------------------------------------------------------------
// wxAnimationCtrl - event handlers
// ----------------------------------------------------------------------------

void wxAnimationCtrl::OnPaint( wxPaintEvent& ( event ) ) {
  // VERY IMPORTANT: the wxPaintDC *must* be created in any case
  wxPaintDC dc( this );
  if( m_backingStore.IsOk() ) {
    dc.DrawBitmap( m_backingStore, 0, 0, false /* no mask */ );
  } else {
    DisposeToBackground( dc );
  }
}

void wxAnimationCtrl::OnTimer( wxTimerEvent &( event ) ) {
  m_currentFrame++;
  if( m_currentFrame == m_animation.GetFrameCount() ) {
    if( !m_looped ) {
      Stop();
      return;
    } else
    { m_currentFrame = 0; }     // let's restart
  }
  IncrementalUpdateBackingStore();
  wxClientDC dc( this );
  DrawCurrentFrame( dc );
  int delay = m_animation.GetDelay( m_currentFrame );
  if( delay == 0 ) {
    delay = 1;
  }      // 0 is invalid timeout for wxTimer.
  m_timer.Start( delay, true );
}

void wxAnimationCtrl::OnSize( wxSizeEvent &( event ) ) {
  if( m_animation.IsOk() ) {
    if( IsPlaying() ) {
      if( !RebuildBackingStoreUpToFrame( m_currentFrame ) ) {
        Stop();
      }     // in case we are playing
    }
  }
}

#endif      // wxUSE_ANIMATIONCTRL

